/* Octopus PDF - A simple document viewer for Android.
   Copyright (C) 2013  Gerald Thaler

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>. */
package de.geraldthaler.octopuspdf.control;

import static android.opengl.GLES10.GL_MODELVIEW;
import static android.opengl.GLES10.glLoadIdentity;
import static android.opengl.GLES10.glMatrixMode;
import static android.opengl.GLES10.glRotatef;
import static android.opengl.GLES10.glScalef;
import static android.opengl.GLES10.glTranslatef;
import static java.lang.Math.max;
import static java.lang.Math.round;
import de.geraldthaler.octopuspdf.docrender.DocRenderClient;
import de.geraldthaler.octopuspdf.docrender.DocRenderTask;
import de.geraldthaler.octopuspdf.docrender.DocRenderer;
import de.geraldthaler.octopuspdf.func.ProcObj;
import de.geraldthaler.octopuspdf.func.ProcObjObj;
import de.geraldthaler.octopuspdf.gfx.Rect;
import de.geraldthaler.octopuspdf.gfx.Size;
import de.geraldthaler.octopuspdf.gfx.SizeF;
import de.geraldthaler.octopuspdf.memory.DirectByteBufferPool;
import de.geraldthaler.octopuspdf.memory.Disposable;
import de.geraldthaler.octopuspdf.view.TexturePool;

/**
 * @author Gerald Thaler
 */
public final class Sheet implements Disposable, DocRenderClient {
    private static final int MIN_HEIGHT = 64;

    private boolean dirty, docRenderRequested;
    private final DirectByteBufferPool directByteBufferPool;
    private final DocRenderer docRenderer;
    private int glGeneration;
    private Mesh mesh; 
    private final int[] pageNrs = new int[2];
    private boolean[] pageSizeRequested = new boolean[2];
    private int leftWidth, rightWidth, totalWidth;
    private int height;
    private float effectiveWidth, effectiveHeight;
    private float scaleX, scaleY;
    private final boolean singlePage;
    private final TexturePool texturePool;

    public Sheet(final int glGeneration, final TexturePool texturePool, final DirectByteBufferPool directByteBufferPool,
            final DocRenderer docRenderer, final int pageNrLeft, final int pageNrRight) {
        this.glGeneration = glGeneration;
        this.texturePool = texturePool;
        this.directByteBufferPool = directByteBufferPool;
        this.docRenderer = docRenderer;
        pageNrs[0] = pageNrLeft;
        pageNrs[1] = pageNrRight;
        mesh = new Mesh(directByteBufferPool);
        singlePage = pageNrLeft < 0 || pageNrRight < 0;
    }

    @Override
    public void dispose() {
        docRenderer.cancelAll(this);
        mesh.dispose();
    }

    public void dispose(final int glGeneration) {
        docRenderer.cancelAll(this);
        mesh.dispose(glGeneration);
    }

    public void draw(final float x, final float y, final float brightness, final float angle, final float rotX,
            final float rotY) {
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();
        glTranslatef(x, y, 0f);
        if (angle != 0f) {
            glTranslatef(rotX, rotY, 0f);
            glRotatef(angle, 0f, 0f, -1f);
            glTranslatef(-rotX, -rotY, 0f);
        }
        glScalef(scaleX, scaleY, 1f);
        glTranslatef(-mesh.getWidth() * 0.5f, -mesh.getHeight() * 0.5f, 0f);
        mesh.draw(brightness);
    }

    public float getWidth() {
        return effectiveWidth;
    }

    public float getHeight() {
        return effectiveHeight;
    }

    public void onSurfaceCreated(final int glGeneration) {
        mesh.dispose(this.glGeneration);
        mesh = new Mesh(directByteBufferPool);
        this.glGeneration = glGeneration;
        requestDocRender();
        computeScale();
    }

    public void setHeight(final float height) {
        final int h = max(round(height), MIN_HEIGHT);
        final SizeF pageSizeLeft = getPageSize(0);
        final SizeF pageSizeRight = getPageSize(1);
        final int lw = pageSizeLeft == null ? 0 : round(pageSizeLeft.getWidth() * h / pageSizeLeft.getHeight());
        final int rw = pageSizeRight == null ? 0 : round(pageSizeRight.getWidth() * h / pageSizeRight.getHeight());
        if (h == this.height && lw == leftWidth && rw == rightWidth)
            return;
        leftWidth = lw;
        rightWidth = rw;
        totalWidth = lw + rw;
        this.height = h;
        effectiveWidth = totalWidth;
        effectiveHeight = h;
        requestDocRender();
        computeScale();
    }

    void onRenderComplete(final DocRenderTask[] tasks, final long[] pixmaps) {
        docRenderRequested = false;
        mesh.dispose(glGeneration);
        mesh = new Mesh(texturePool, directByteBufferPool, tasks, pixmaps);
        computeScale();
        if (dirty)
            requestDocRender();
    }

    private void computeScale() {
        scaleX = (float) totalWidth / mesh.getWidth();
        scaleY = (float) height / mesh.getHeight();
    }

    private SizeF getPageSize(final int pageId) {
        final int pageNr = pageNrs[pageId];
        if (pageNr < 0)
            return null;
        SizeF size = docRenderer.getPageSize(pageNr);
        if (size != null)
            return size;
        if (!pageSizeRequested[pageId]) {
            docRenderer.requestPageSize(this, pageNr, new ProcObj<SizeF>() {
                @Override
                public void invoke(final SizeF size) {
                    setHeight(getHeight());
                }
            });
            pageSizeRequested[pageId] = true;
        }
        return docRenderer.getEstimatedPageSize(pageNr);
    }

    private void requestDocRender() {
        if (docRenderRequested) {
            dirty = true;
            return;
        }
        dirty = false;
        final DocRenderTask[] tasks;
        if (singlePage) {
            final int index = pageNrs[0] >= 0 ? 0 : 1;
            final int pageNr = pageNrs[index];
            final Size size = new Size(totalWidth, height);
            final Rect region = new Rect(0, 0, totalWidth, height);
            final DocRenderTask task = new DocRenderTask(pageNr, size, region);
            tasks = new DocRenderTask[] {task};
        } else {
            final Size size1 = new Size(leftWidth, height);
            final Size size2 = new Size(rightWidth, height);
            final Rect region1 = new Rect(0, 0, leftWidth, height);
            final Rect region2 = new Rect(0, 0, rightWidth, height);
            final DocRenderTask task1 = new DocRenderTask(pageNrs[0], size1, region1);
            final DocRenderTask task2 = new DocRenderTask(pageNrs[1], size2, region2);
            tasks = new DocRenderTask[] {task1, task2};
        }
        docRenderer.requestRender(this, tasks, new ProcObjObj<DocRenderTask[], long[]>() {
            @Override
            public void invoke(final DocRenderTask[] tasks, final long[] pixmaps) {
                onRenderComplete(tasks, pixmaps);
            }
        });
        docRenderRequested = true;
    }
}
